﻿using CommonEngine;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;

namespace CommonDevelop.Operator
{
    /// <summary>
    /// JSON Web Token接口
    /// </summary>
    public interface IJwtFactory
    {
        Task<string> GenerateJWT(string account, string Token, string UserId, string BusinessID);
        Task<string> GenerateJWTToken(string account, ClaimsIdentity identity);
        /// <summary>
        /// 生成编码Token
        /// </summary>
        Task<JwtToken> GenerateEncodedToken(string account, string refreshToken, ClaimsIdentity identity);
        /// <summary>
        /// 生成证件
        /// </summary>
        ClaimsIdentity GenerateClaimsIdentity(string Token, string UserId, string BusinessID);
    }

    /// <summary>
    /// 服务器认证工厂
    /// </summary>
    public class JwtFactory : IJwtFactory
    {
        private readonly JwtOptions _jwtOptions;
        /// <summary>
        /// 构造
        /// </summary>
        public JwtFactory(IOptions<JwtOptions> jwtOptions)
        {
            this._jwtOptions = jwtOptions.Value;
            //验证配置
            ThrowIfInvalidOptions(this._jwtOptions);
        }

        /// <summary>
        /// 生成编码Token
        /// </summary>
        public async Task<string> GenerateJWT(string account, string Token, string UserId, string BusinessID)
        {
            if (account.IsEmpty() || Token.IsEmpty() || UserId.IsEmpty() || BusinessID.IsEmpty())
                return string.Empty;

            var claimsIdentity = this.GenerateClaimsIdentity(Token, UserId, BusinessID);
            return await this.GenerateJWTToken(account, claimsIdentity);
        }

        /// <summary>
        /// 生成编码Token
        /// </summary>
        public async Task<string> GenerateJWTToken(string account, ClaimsIdentity identity)
        {
            //设计声明
            var claims = new List<Claim>
            {
                new Claim(JwtRegisteredClaimNames.Sub, account),
                new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
                new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64),

                identity.FindFirst(ClaimTypes.Authentication),
                identity.FindFirst(ClaimTypes.UserData),
                identity.FindFirst(ClaimTypes.GroupSid)
            };

            // Create the JWT security token and encode it.
            var jwt = new JwtSecurityToken(
                issuer: this._jwtOptions.Issuer,
                audience: this._jwtOptions.Audience,
                claims: claims,
                notBefore: this._jwtOptions.NotBefore,
                expires: this._jwtOptions.Expiration,
                signingCredentials: this._jwtOptions.SigningCredentials);

            //生成Token
            var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
            return encodedJwt;
        }

        /// <summary>
        /// 生成编码Token
        /// </summary>
        public async Task<JwtToken> GenerateEncodedToken(string account, string refreshToken, ClaimsIdentity identity)
        {
            //设计声明
            var claims = new List<Claim>
            {
                new Claim(JwtRegisteredClaimNames.Sub, account),
                new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
                new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64),

                identity.FindFirst(ClaimTypes.Authentication),
                identity.FindFirst(ClaimTypes.UserData),
                identity.FindFirst(ClaimTypes.GroupSid)
            };

            // Create the JWT security token and encode it.
            var jwt = new JwtSecurityToken(
                issuer: this._jwtOptions.Issuer,
                audience: this._jwtOptions.Audience,
                claims: claims,
                notBefore: this._jwtOptions.NotBefore,
                expires: this._jwtOptions.Expiration,
                signingCredentials: this._jwtOptions.SigningCredentials);

            //生成Token
            var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

            var token = new JwtToken
            {
                access_token = encodedJwt,
                refresh_token = refreshToken,
                expires_in = (int)_jwtOptions.ValidFor,
                token_type = "Bearer"
            };

            return token;
        }

        /// <summary>
        /// 生成证件
        /// </summary>
        public ClaimsIdentity GenerateClaimsIdentity(string Token, string UserId, string BusinessID)
        {
            var claimsIdentity = new ClaimsIdentity(new GenericIdentity(UserId, "Token"));
            claimsIdentity.AddClaim(new Claim(ClaimTypes.SerialNumber, Token));
            claimsIdentity.AddClaim(new Claim(ClaimTypes.UserData, UserId));
            claimsIdentity.AddClaim(new Claim(ClaimTypes.GroupSid, BusinessID));

            return claimsIdentity;
        }

        /// <summary>
        /// 从Unix纪元（1970年1月1日，UTC午夜）转换为秒的日期
        /// </summary>
        private static long ToUnixEpochDate(DateTime date)
        {
            double a = (date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds;
            return (long)Math.Round(a);
        }

        /// <summary>
        /// 无效选项时抛出
        /// </summary>
        private static void ThrowIfInvalidOptions(JwtOptions options)
        {
            if (options == null)
                throw new ArgumentNullException(nameof(options));

            if (options.SigningCredentials == null)
                throw new ArgumentNullException(nameof(JwtOptions.SigningCredentials));

            if (options.JtiGenerator == null)
                throw new ArgumentNullException(nameof(JwtOptions.JtiGenerator));

            //必须是非零有效期
            if (options.ValidFor <= TimeSpan.Zero.TotalMilliseconds)
                throw new ArgumentException("Must be a non-zero ValidFor.", nameof(JwtOptions.ValidFor));
        }
    }
}
